/*
  - @Author: pardon110
  - @Date: 2024-03-26 12:37:37
  - @LastEditors: pardon110@qq.com
  - @LastEditTime: 2024-03-27 10:15:00
  - @FilePath: \days7\orm\reflect-schema\schema\schema.go
  - @Description:  数据库列/表在go语言中的表现

// CREATE TABLE `User` (`Name` text PRIMARY KEY, `Age` integer);
*/
package schema

import (
	"go/ast"
	"reflect"

	"days7.test/orm/reflect-schema/dialect"
)

// Field represents a column of database
type Field struct {
	Name string
	Type string
	Tag  string
}

// Schema represents a table of database
type Schema struct {
	Model      interface{}
	Name       string
	Fields     []*Field // 字段结构信息
	FieldNames []string // 包含所有的字段名(列名)
	fieldMap   map[string]*Field
}

func (schema *Schema) GetField(name string) *Field {
	return schema.fieldMap[name]
}

// s := orm.NewEngine("sqlite3", "orm.db").NewSession()
// u1 := &User{Name: "Tom", Age: 18}
// u2 := &User{Name: "Sam", Age: 25}
// s.Insert(u1, u2, ...)
//
// 根据数据库中列的顺序，从对象中找到对应的值，按顺序平铺
// 即 u1、u2 转换为 ("Tom", 18), ("Same", 25)
// Values return the values of dest's member variables
func (schema *Schema) RecordValues(dest interface{}) []interface{} {
	// Indirect 接收指针类型
	destValue := reflect.Indirect(reflect.ValueOf(dest))
	var fieldValues []interface{}
	for _, field := range schema.Fields {
		// destValue struct 通过结构体字段名获取对应的值
		fieldValues = append(fieldValues, destValue.FieldByName(field.Name).Interface())
	}
	return fieldValues
}

type ITableName interface {
	TableName() string
}

// Parse a struct to a Schema instance
// 将任意的对象解析为 Schema 实例
func Parse(dest interface{}, d dialect.Dialect) *Schema {
	// 获取具体类型 dest 无视值类型或指针类型
	modelType := reflect.Indirect(reflect.ValueOf(dest)).Type()
	var tableName string
	t, ok := dest.(ITableName)
	if !ok {
		tableName = modelType.Name() // 使用类型名称作为表名
	} else {
		tableName = t.TableName()
	}

	schema := &Schema{
		Model:    dest,
		Name:     tableName,
		fieldMap: make(map[string]*Field),
	}

	for i := 0; i < modelType.NumField(); i++ {
		p := modelType.Field(i) // reflect.StructField
		// 判断字段为非嵌入字段且可导出
		if !p.Anonymous && ast.IsExported(p.Name) {
			field := &Field{
				Name: p.Name,
				Type: d.DataTypeOf(reflect.Indirect(reflect.New(p.Type))),
			}

			if v, ok := p.Tag.Lookup("geeorm"); ok {
				field.Tag = v
			}

			schema.Fields = append(schema.Fields, field)
			schema.FieldNames = append(schema.FieldNames, p.Name)
			// 维护一个字段名至字段处的实例映射
			schema.fieldMap[p.Name] = field
		}
	}
	return schema
}
